package cbr;

import java.io.PrintStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import base.WargusGameState;
import base.WargusMap;
import base.WargusPlayer;
import base.WargusStateImporter;
import base.WargusUnit;

public class CBRAdaptationCoordinates {
	
	static final int window_size = 7;
//	static final float cell_weight[]={32,3,1};	// For window size 5
//	static final float total_weight = 72; 
	static final float cell_weight[]={48,5,2,1};	// For window size 7
	static final float total_weight = 144 ;
	public static PrintStream m_logger = null;
	
	/*
	 * Find Most similar Map Coordinate  in the current Map
	 */
	public static Coordinate findMostSimilarCoordinates(int x, int y,
			WargusGameState a_gamestate, WargusGameState a_Context,
			int buildSpace,int a_playerID) throws Exception {		
		m_logger.println("CBR: Addapting coordinates " + x + " " + y + " with required build space of " + buildSpace + "x" + buildSpace);
		System.out.println("CBR: Addapting coordinates " + x + " " + y + " with required build space of " + buildSpace + "x" + buildSpace);
		
		Vector< Vector <Character> > pattern1,pattern2;
		int i,j;
		float best_d=0,d,d1,d2;
		int best_x=x,best_y=y;
		boolean first=true;
		
		pattern1 = getTerrainPattern(x,y,window_size,a_Context.getMap());
		
		{
			m_logger.println("Target pattern:");
			System.out.println("Target pattern:");
			for(i = 0; i < window_size; i++ ) {
				for(j = 0;j < window_size;j++) {
					m_logger.print(""+pattern1.get(i).get(j));
					System.out.print(""+pattern1.get(i).get(j));}
				m_logger.println("");
				System.out.println("");
			}
		}

		/* Simple Version that considers all the game field: */ 
		for(i=0;i<a_gamestate.getMap().get_height();i++) {
			for(j=0;j<a_gamestate.getMap().get_width();j++) {
				
				if (enoughBuildingSpace(j,i,buildSpace,a_gamestate)) {
					pattern2 = getTerrainPattern(j,i,window_size,a_gamestate.getMap());
					
					d1 = ComparePattern(pattern1,pattern2);
					d2 = CompareUnitLocation(j,i,a_gamestate,x,y,a_Context,a_playerID);
					
					d = d1*0.66f + d2*0.33f;
					
//					System.out.println("Location: " + j + "," + i + " -> " + d1 + "," + d2 +" : " + d);
				
					
					if (first || d<best_d) {
/*
						System.out.println("Location: " + j + "," + i + " -> " + d1 + "," + d2 +" : " + d);
						{
							pattern2 = getTerrainPattern(j,i,window_size,a_gamestate.getMap());
							int a,b;
							for(a = 0; a < window_size; a++ ) {
								for(b = 0;b < window_size;b++) {
									System.out.print(""+pattern2.get(a).get(b));
								}			
								System.out.println("");
							}
						}					
*/										
						best_d = d;
						best_x = j;
						best_y = i;
						first=false;
					}
				} 
			}
		}
				
		m_logger.println("CBR: findMostSimilarCoordinates, best coordinates found are " + best_x + " " + best_y + " with distance: " + best_d);
		System.out.println("CBR: findMostSimilarCoordinates, best coordinates found are " + best_x + " " + best_y + " with distance: " + best_d);
		{
			pattern2 = getTerrainPattern(best_x,best_y,window_size,a_gamestate.getMap());
			int a,b;
			for(a = 0; a < window_size; a++ ) {
				for(b = 0;b < window_size;b++) {
					m_logger.print(""+pattern2.get(a).get(b));
					System.out.print(""+pattern2.get(a).get(b));
				}
				
				m_logger.println("");
				System.out.println("");
			}
		}					
		
		Coordinate p = new Coordinate();
		p.x  = best_x;
		p.y  = best_y;
		return p;
	}
	
	
	/*
	 * Find Most similar Map Coordinate in the current Map, and reachable from the specified start_x,start_Y coordinates
	 */
	public static Coordinate findMostSimilarCoordinatesReachable(int start_x,int start_y,int x,int y,WargusGameState a_gamestate,WargusGameState a_Context,int buildSpace,int a_playerID) throws Exception {		
		m_logger.println("CBR: Addapting coordinates " + x + " " + y + " with required build space of " + buildSpace + "x" + buildSpace);
		System.out.println("CBR: Addapting coordinates " + x + " " + y + " with required build space of " + buildSpace + "x" + buildSpace);
		
		Vector< Vector <Character> > pattern1,pattern2;
		int i,j;
		float best_d=0,d,d1,d2;
		int best_x=x,best_y=y;
		boolean first=true;
		
		pattern1 = getTerrainPattern(x,y,window_size,a_Context.getMap());
		
		System.out.println("Getting a pattern of size " + window_size + "x" + window_size + " from " + x + "," + y + " from the context map:");
		System.out.println(a_Context.getMap().toStringTerrain());
		
		
		{
			m_logger.println("Target pattern:");
			System.out.println("Target pattern:");
			for(i = 0; i < window_size; i++ ) {
				for(j = 0;j < window_size;j++) {
					m_logger.print(""+pattern1.get(i).get(j));
					System.out.print(""+pattern1.get(i).get(j));}
				m_logger.println("");
				System.out.println("");
			}
		}

		/* Consider only the reachable coordinates: */ 
		{
			int cycles = 0;
			int current;
			char t;
			LinkedList<Integer> open = new LinkedList<Integer>();
			int map_size = a_gamestate.getMap().get_height()*a_gamestate.getMap().get_width();
			boolean []closed = new boolean[map_size];

			for(i=0;i<map_size;i++) closed[i]=false;
			open.add(new Integer(start_x+start_y*a_gamestate.getMap().get_width()));
			while(open.size()>0) {
				cycles++;
				current = open.remove();
				
				if (!closed[current]) {
					
					// Consider the point:
					j = current%a_gamestate.getMap().get_width();
					i = current/a_gamestate.getMap().get_width();
					if (enoughBuildingSpace(j,i,buildSpace,a_gamestate)) {
						pattern2 = getTerrainPattern(j,i,window_size,a_gamestate.getMap());
						
						d1= ComparePattern(pattern1,pattern2);
						d2 = CompareUnitLocation(j,i,a_gamestate,x,y,a_Context,a_playerID);						
						d = d1*0.66f + d2*0.33f;	
												
						if (first || d<best_d) {
							best_d = d;
							best_x = j;
							best_y = i;
							first=false;
						}
					} 
					
					if (j<a_gamestate.getMap().get_width()-1) {
						t = a_gamestate.getMap().get_map(j+1, i);
						if (!closed[current+1] &&
							(t == WargusStateImporter.TERRAIN_NONE || t == WargusStateImporter.TERRAIN_GRASS /*|| 
							 t == WargusStateImporter.TERRAIN_UNIT || t == WargusStateImporter.TERRAIN_UNITOPP*/)) {
							if (!open.contains(current+1)) open.add(current+1);
						}
					}
					if (j>0) {
						t = a_gamestate.getMap().get_map(j-1, i);
						if (!closed[current-1] &&
							(t == WargusStateImporter.TERRAIN_NONE || t == WargusStateImporter.TERRAIN_GRASS /*|| 
							 t == WargusStateImporter.TERRAIN_UNIT || t == WargusStateImporter.TERRAIN_UNITOPP*/)) {
							if (!open.contains(current-11)) open.add(current-1);
						}
					}
					if (i<a_gamestate.getMap().get_height()-1) {
						t = a_gamestate.getMap().get_map(j, i+1);
						if (!closed[current+a_gamestate.getMap().get_width()] &&
							(t == WargusStateImporter.TERRAIN_NONE || t == WargusStateImporter.TERRAIN_GRASS /*|| 
							 t == WargusStateImporter.TERRAIN_UNIT || t == WargusStateImporter.TERRAIN_UNITOPP*/)) {
							if (!open.contains(current+current%a_gamestate.getMap().get_width())) open.add(current+a_gamestate.getMap().get_width());
						}
					}
					if (i>0) {
						t = a_gamestate.getMap().get_map(j, i-1);
						if (!closed[current-a_gamestate.getMap().get_width()] &&
							(t == WargusStateImporter.TERRAIN_NONE || t == WargusStateImporter.TERRAIN_GRASS /*|| 
							 t == WargusStateImporter.TERRAIN_UNIT || t == WargusStateImporter.TERRAIN_UNITOPP*/)) {
							if (!open.contains(current-current%a_gamestate.getMap().get_width())) open.add(current-a_gamestate.getMap().get_width());
						}
					}					
					
					closed[current]=true;
				}
			}
			
			// Show parts of the map considered:
			
			System.out.println("Portion of the map considered: (in " + cycles + " cycles)");
			for(i=0;i<map_size;i++) {
				if ((best_x+best_y*a_gamestate.getMap().get_width())==i) {
					System.out.print("X");
				} else if ((start_x+start_y*a_gamestate.getMap().get_width())==i) {
					System.out.print("O");
				} else {
					if (closed[i]) System.out.print("*");
							  else System.out.print(".");
				}
				if (((i+1)%a_gamestate.getMap().get_width())==0) System.out.println("");
			}
			
		}

		
		if (first) {
			m_logger.println("CBR: findMostSimilarCoordinatesReachable, no valid coordinates could be found!");
			System.out.println("CBR: findMostSimilarCoordinatesReachable, no valid coordinates could be found!");			
		} else {
			m_logger.println("CBR: findMostSimilarCoordinatesReachable, best coordinates found are " + best_x + " " + best_y + " with distance: " + best_d);
			System.out.println("CBR: findMostSimilarCoordinatesReachable, best coordinates found are " + best_x + " " + best_y + " with distance: " + best_d);
			{
				pattern2 = getTerrainPattern(best_x,best_y,window_size,a_gamestate.getMap());
				int a,b;
				for(a = 0; a < window_size; a++ ) {
					for(b = 0;b < window_size;b++) {
						m_logger.print(""+pattern2.get(a).get(b));
						System.out.print(""+pattern2.get(a).get(b));
					}
					
					m_logger.println("");
					System.out.println("");
				}
			}					
		}
		
		if (first) return null;
		Coordinate p = new Coordinate();
		p.x = best_x;
		p.y = best_y;		
		return p;
	}	
	
	
	/*
	 * Get a 5*5 Matrix that match a given 5*5 pattern
	 */
	public static Vector< Vector <Character> > getTerrainPattern(int x,int y,int size,WargusMap map)
	{
		Vector< Vector <Character> > pattern = new Vector< Vector<Character>>();
		Vector <Character> row;
		int i,j;
		x-=size/2;
		y-=size/2;
		for(i = 0; i < size; i++ ) {
			row = new Vector<Character>();
			for(j = 0;j < size;j++) {
				row.add(new Character(map.get_map(j+x,i+y)));
			}
			pattern.add(row);
		}		
		
		return pattern;
	}
	
	
	public static boolean enoughBuildingSpace(int x,int y,int size,WargusGameState state) {
		int i,j;
		for(i = 0; i < size; i++ ) {
			for(j = 0;j < size;j++) {
				if (state.getMap().get_map(x+j,y+i)!=WargusStateImporter.TERRAIN_GRASS) return false;
			}
		}		
		return true;
	}
	

	public static float ComparePattern(Vector< Vector <Character> > pattern1,Vector< Vector <Character> > pattern2)
	{
		int i,j;
		float distance = 0;		
		
		for(i=0;i< pattern1.size();i++)
			for(j=0;j< pattern1.get(i).size();j++)
			{
				int ring = Math.max(Math.abs(i-(window_size/2)), Math.abs(j-(window_size/2)));
				
				if(pattern1.get(i).get(j).equals(pattern2.get(i).get(j))) distance += cell_weight[ring];
			}
		
		return 1 - (distance/total_weight);
	}
	
	public static float CompareUnitLocation(int a_x1,int a_y1,WargusGameState a_gamestate1,int a_x2,int a_y2,WargusGameState a_gamestate2,int a_playerID)
	{
		WargusUnit um1[] = new WargusUnit[11];
		WargusUnit um2[] = new WargusUnit[11];
		float d1,d2;
		int i,j;
		int num1 = 0;
		int num2 = 0;

		// Get the 10 closest units in map1:
		WargusPlayer p1 = a_gamestate1.getPlayer();
		List<WargusPlayer> opl1 = a_gamestate1.getOpponent();
		
		for(WargusUnit u:p1.getUnits()) {
			j=0;
			if (num1>0) {
				do {
					d1 = (um1[j].getLocX()-a_x1)*(um1[j].getLocX()-a_x1) + (um1[j].getLocY()-a_y1)*(um1[j].getLocY()-a_y1);
					d2 = (u.getLocX()-a_x1)*(u.getLocX()-a_x1) + (u.getLocY()-a_y1)*(u.getLocY()-a_y1);
					if (d2>d1) j++;
				} while(d2>d1 && j<num1);
			} // if 
			if (j<10) {
//				System.out.println("Adding(1): " + WargusStateImporter.unitTypeToString(u.getType())); 
				for(i=num1;i>j;i--) um1[i]=um1[i-1];
				um1[j]=u;
				if (num1<10) num1++;
			}
		} // for
		for(WargusPlayer op:opl1) {
			for(WargusUnit u:op.getUnits()) {
				j=0;
				if (num1>0) {
					do {
						d1 = (um1[j].getLocX()-a_x1)*(um1[j].getLocX()-a_x1) + (um1[j].getLocY()-a_y1)*(um1[j].getLocY()-a_y1);
						d2 = (u.getLocX()-a_x1)*(u.getLocX()-a_x1) + (u.getLocY()-a_y1)*(u.getLocY()-a_y1);
						if (d2>d1) j++;
					} while(d2>d1 && j<num1);
				} // if 
				if (j<10) {
//					System.out.println("Adding(1): " + WargusStateImporter.unitTypeToString(u.getType()));
					for(i=num1;i>j;i--) um1[i]=um1[i-1];
					um1[j]=u;
					if (num1<10) num1++;
				}
			} // for		
		} // for
		
		// Get the 10 closest units in map2:
		WargusPlayer p2 = a_gamestate2.getPlayer();
		List<WargusPlayer> opl2 = a_gamestate2.getOpponent();
		
		for(WargusUnit u:p2.getUnits()) {
			j=0;
			if (num2>0) {
				do {
					d1 = (um2[j].getLocX()-a_x1)*(um2[j].getLocX()-a_x1) + (um2[j].getLocY()-a_y1)*(um2[j].getLocY()-a_y1);
					d2 = (u.getLocX()-a_x1)*(u.getLocX()-a_x1) + (u.getLocY()-a_y1)*(u.getLocY()-a_y1);
					if (d2>d1) j++;
				} while(d2>d1 && j<num2);
			} // if 
			if (j<10) {
				for(i=num2;i>j;i--) um2[i]=um2[i-1];
				um2[j]=u;
				if (num2<10) num2++;
			}
		} // for
		for(WargusPlayer op:opl2) {
			for(WargusUnit u:op.getUnits()) {
				j=0;
				if (num2>0) {
					do {
						d1 = (um2[j].getLocX()-a_x1)*(um2[j].getLocX()-a_x1) + (um2[j].getLocY()-a_y1)*(um2[j].getLocY()-a_y1);
						d2 = (u.getLocX()-a_x1)*(u.getLocX()-a_x1) + (u.getLocY()-a_y1)*(u.getLocY()-a_y1);
						if (d2>d1) j++;
					} while(d2>d1 && j<num2);
				} // if 
				if (j<10) {
					for(i=num2;i>j;i--) um2[i]=um2[i-1];
					um2[j]=u;
					if (num2<10) num2++;
				}
			} // for		
		} // for	
		
		// Display by screen:
	/*
		System.out.println("10 units closer to: " + a_x1 + "," + a_y1);
		for(i=0;i<num1;i++) 
			System.out.println("" + (i+1) + " - " + WargusStateImporter.unitTypeToString(um1[i].getType()) + " " + um1[i].getLocX() + "," + um1[i].getLocY());
		System.out.println("10 units closer to: " + a_x2 + "," + a_y2);
		for(i=0;i<num2;i++) 
			System.out.println("" + (i+1) + " - " + WargusStateImporter.unitTypeToString(um2[i].getType()) + " " + um2[i].getLocX() + "," + um2[i].getLocY());
	*/
	
		float dist = 0;
		
		/*
		// Method 1: comparing unit by unit
		for(i=0;i<num1 && i<num2;i++) {
			double du = um1[i].distance(um2[i],0.4f,0.4f,0.0f,0.1f,0.1f);
			double dd = Math.sqrt((um1[i].getLocX()-um2[i].getLocX())*(um1[i].getLocX()-um2[i].getLocX())+
							      (um1[i].getLocY()-um2[i].getLocY())*(um1[i].getLocY()-um2[i].getLocY()));
			dd /= Math.max(Math.sqrt(a_gamestate1.getMap().get_height()*a_gamestate1.getMap().get_height()+
						    		 a_gamestate1.getMap().get_width()*a_gamestate1.getMap().get_width()),
			    		   Math.sqrt(a_gamestate2.getMap().get_height()*a_gamestate2.getMap().get_height()+
					    		     a_gamestate2.getMap().get_width()*a_gamestate2.getMap().get_width()));
			dist += (0.5f*du+0.5f*dd);
//			System.out.println("[" + du + "," + dd + "]");
		}
		if (i==0) dist = 0;
		     else dist = dist/(float)i;
		*/
		
		// Method 2: extrcting some features:
		{
			int c1=0,c2=0;
			float avg_distance_self1 = 0.0f;
			float avg_distance_opponents1 = 0.0f;
			float avg_distance_self2 = 0.0f;
			float avg_distance_opponents2 = 0.0f;
			
			for(i=0;i<num1;i++) {
				double dd = Math.sqrt((um1[i].getLocX()-a_x1)*(um1[i].getLocX()-a_x1)+
					      (um1[i].getLocY()-a_y1)*(um1[i].getLocY()-a_y1));
				dd /= Math.max(Math.sqrt(a_gamestate1.getMap().get_height()*a_gamestate1.getMap().get_height()+
							    		 a_gamestate1.getMap().get_width()*a_gamestate1.getMap().get_width()),
				    		   Math.sqrt(a_gamestate2.getMap().get_height()*a_gamestate2.getMap().get_height()+
						    		     a_gamestate2.getMap().get_width()*a_gamestate2.getMap().get_width()));
				if (um1[i].getPlayerID()==a_playerID) {
					c1++;
					avg_distance_self1+=dd;
				} else {
					c2++;
					avg_distance_opponents1+=dd;
				}
			}
			if (c1>0) avg_distance_self1/=(float)c1;			
			if (c2>0) avg_distance_opponents1/=(float)c2;		
			
			c1=c2=0;
			for(i=0;i<num2;i++) {
				double dd = Math.sqrt((um2[i].getLocX()-a_x2)*(um2[i].getLocX()-a_x2)+
					      (um2[i].getLocY()-a_y2)*(um2[i].getLocY()-a_y2));
				dd /= Math.max(Math.sqrt(a_gamestate1.getMap().get_height()*a_gamestate1.getMap().get_height()+
							    		 a_gamestate1.getMap().get_width()*a_gamestate1.getMap().get_width()),
				    		   Math.sqrt(a_gamestate2.getMap().get_height()*a_gamestate2.getMap().get_height()+
						    		     a_gamestate2.getMap().get_width()*a_gamestate2.getMap().get_width()));
				if (um2[i].getPlayerID()==a_playerID) {
					c1++;
					avg_distance_self2+=dd;
				} else {
					c2++;
					avg_distance_opponents2+=dd;
				}
			}
			if (c1>0) avg_distance_self2/=(float)c1;			
			if (c2>0) avg_distance_opponents2/=(float)c2;	
			dist = 0.5f*Math.abs(avg_distance_self1-avg_distance_self2)+0.5f*Math.abs(avg_distance_opponents1-avg_distance_opponents2);
//			System.out.println("[" + avg_distance_self1 + "," + avg_distance_opponents1 + "," + avg_distance_self2 + "," + avg_distance_opponents2 + "]");
			
		}
//		System.out.println("[" + dist + "]");
		
		return dist;
	}
}
